Python数据可视化

  • 1. Matplotlib入门
    • 1.1 引例:Plotly VS. Matplotlib
    • 1.2 两个不足及解决方案
    • 1.3 绘图基本流程
      • 练习0. 绘制sin曲线和cos曲线
  • 2. pandas对象绘制基本图形
    • 2.1 线形图 Line
      • 例1-1 Series对象进行绘图
      • 例1-2 DataFrame对象进行绘图
      • 时间序列分析
      • 练习1. 绘制2018年12月每天销售额和利润的时间序列图
      • 练习2. 绘制2018年每月销售额时间序列图
      • 练习3. 绘制2018年不同产品类别的每月销售额的时间序列图
    • 2.2 柱状图和条形图 Bar
      • 例2-1 Series对象调用plot.bar()函数
      • 例2-2 DataFrame对象调用plot.bar()函数
      • 练习4. 绘制商品子类别销售额的柱状图
      • 练习5. 绘制商品子类别中不同客户细分销售额的堆积柱状图
    • 2.3 饼图 Pie
      • 例3-1 Series对象调用plot.pie() 函数
      • 练习6. 绘制不同地区销售额占比情况的饼图
      • 练习7. 绘制办公用品各子类别销售额占比情况的饼图
    • 2.4 直方图 Hist
      • 例4-1 Series对象调用plot.hist() 函数
      • 练习8. 绘制每笔订单利润分布情况的直方图
    • 2.5 散点图和气泡图 Scatter
      • 例5-1 使用plt.scatter()函数绘制散点图和气泡图
      • 练习9. 绘制商品(设备)销售额和利润的散点图
      • 练习10. 绘制2018年浙江省每笔订单销售额和利润的气泡图
  • 3. pandas对象绘制多子图 Subplots
      • 练习11. 绘制两行一列多子图
      • 练习12. 绘制一行两列多子图

1. Matplotlib入门¶

可视化和绘图是数据分析中重要的任务之一,可以看作是数据分析的最后一个步骤。Python有很多附加库可以用来制作静态或动态的可视化文件:

  • Matplotlib:最经典的Python 2D绘图库,以各种硬拷贝格式和跨平台的交互式环境生成出版质量级别的图形。取名源于 MATLAB+Plot+Library,绘图风格模仿MATLAB。
  • Seaborn:由于Matplotlib风格有些偏古典,Python开源社区开发了Seaborn绘图模块,本质上是对Matplotlib的封装,绘图效果更符合现代人的审美观。
  • Plotly:以上两个库存在一个很大的缺陷:属于静态的绘图模块,绘制出的图像是静态的,交互式效果不足。目前GitHub开源网站最火的绘图工具叫做Plotly,是一个基于JavaScript的动态绘图模块,可以实现动态交互效果。

尽管Python目前已发展出许多绘图库进行可视化,但Matplotlib的地位仍然不可撼动,其创建之初就是一个用于生成出版级质量图表的绘图包,因此我们仍然需要学习Matplotlib进行绘图。

1.1 引例:Plotly VS. Matplotlib¶

In [1]:
# Sample with Matplotlib
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 2, 100)  # linspace()函数返回100个0-2均匀分布的样本

plt.plot(x, x, label='linear') 
plt.plot(x, x**2,label='quadratic') 
plt.plot(x, x**3, label='cubic')

plt.title('Simple Plot')
plt.xlabel('x 轴')
plt.ylabel('y 轴')

plt.legend()
plt.show()
/Users/yvonne/opt/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 36724 (\N{CJK UNIFIED IDEOGRAPH-8F74}) missing from current font.
  fig.canvas.print_figure(bytes_io, **kw)
In [1]:
# Sample with Plotly
# 导入所需的模块
import numpy as np
import plotly.graph_objects as go

x = np.linspace(0,2,100)

# 使用go.Figure()函数创建一个图形fig
fig = go.Figure()

# 使用add_trace()函数向fig中添加trace(画轨、画迹、画痕)
fig.add_trace(go.Scatter(x=x,y=x,name='linear'))
fig.add_trace(go.Scatter(x=x,y=x**2,name='quadratic'))
fig.add_trace(go.Scatter(x=x,y=x**3,name='cubic'))

# 使用update_layout()函数设置fig的画面布局
fig.update_layout(
    title='Simple Plot',
    xaxis=dict(title='x轴'),
    yaxis=dict(title='y轴'))

# 将图形fig输出
fig.show()

1.2 两个不足及解决方案¶

针对Matplotlib存在的不足,我们通过以下两个方法来解决。

  1. 针对Matplotlib绘制的图片精度低、无法交互的问题,在导入库的同时加上如下魔术指令:%matplotlib notebook,即可在jupyter notebook行内形成交互式的图表。如果无需交互,也可通过魔术指令%config InlineBackend.figure_format = 'retina'生成高精度图片。
  2. 针对Matplotlib绘制的图片无法支持中文字符,在绘图时加上如下代码,指定中文字体即可:plt.rcParams['font.sans-serif'] = ['SimHei'],指定中文后负号无法正常显示,需再添加plt.rcParams['axes.unicode_minus'] = False

Mac电脑解决方案:

  1. Mac中可以通过执行魔术指令:%matplotlib,弹出精度高、可交互对话框。如果无需交互,也可通过魔术指令%config InlineBackend.figure_format = 'retina'生成高精度图片。
  2. 先通过matplotlib.font_manager.fontManager.ttflist查看以安装的字体,然后使用以下两句话实现支持中文字符和显示负号: plt.rcParams['font.sans-serif'] = ['Heiti TC'] plt.rcParams['axes.unicode_minus'] = False

注意,操作系统不同,IDE不同(Jupyter Notebook, JupyterLab, VSCode, PyCharm),所使用的魔术指令会有不同,大家可自行查找并解决。

In [1]:
# Sample with Matplotlib
import matplotlib.pyplot as plt
import numpy as np
# %matplotlib notebook
%config InlineBackend.figure_format = 'retina'

x = np.linspace(0, 2, 100)  # linspace()函数返回100个0-2均匀分布的样本

plt.plot(x, x, label='linear') 
plt.plot(x, x**2,label='quadratic') 
plt.plot(x, x**3, label='cubic')

plt.title('Simple Plot')
plt.xlabel('x 轴')
plt.ylabel('y 轴')

plt.rcParams['font.sans-serif'] = ['Heiti TC']  # 设置中文字体,可以更改为'SimHei'/'DengXian'
plt.rcParams['axes.unicode_minus'] = False  # 设置成中文字体后正常显示负号
plt.tight_layout()  # 紧密布局,使中文标题和文字完整显示

plt.legend()
plt.show()

1.3 绘图基本流程¶

根据引例,绘制一张图片(一对轴)时的绘图流程可以总结如下:

  1. 导入绘图模块(matplotlib是整个包,而matplotlib.pyplot是matplotlib中的一个模块):import matplotlib.pyplot as plt,并在Jupyter notebook中执行魔术指令:%matplotlib notebook;
  2. 使用plt调用各类函数绘制基本图形,比如主函数plt.plot()绘制线形图,plt.bar()绘制柱状图,plt.scatter()绘制散点图,plt.hist()绘制直方图……;
  3. 使用plt调用其它函数进一步丰富图片,比如plt.title()设置标题,plt.xlabel(),plt.ylabel()设置X\Y轴标签,plt.legend()显示图例,plt.annotate()设置注释文本……;
  4. 如果输出的文本中有中文,需要对中文字体进行指定plt.rcParams['font.sans-serif'] = ['SimHei'],指定中文后负号无法正常显示,需再添加plt.rcParams['axes.unicode_minus'] = False;
  5. 使用plt.show()将图片显示出来,使用plt.savefig()保存图片。

绘制的图片结构如下图所示。 image.png

In [3]:
# 根据上述绘图基本框架,再写一次引例并进一步细化
import numpy as np
# 绘图模块的导入惯例以及魔术指令
import matplotlib.pyplot as plt
%matplotlib notebook

x = np.linspace(0, 2, 100)

# ----------**-----以下是使用plt调用各类函数绘制基本图形-----**-----------

# 使用.plot方法绘制多条线形图,第一次调用.plot时会创建轴,随后的多次调用.plot会在相同的轴内绘制线形图
plt.plot(x, x, label='linear') 
plt.plot(x, x**2,label='quadratic') 
plt.plot(x, x**3, label='cubic')  # 每条线形图设置了标签label

# ----------**-----以下是使用plt调用其它函数进一步丰富图片-----**-----------

# 设置图片的标题
plt.title('Simple Plot', fontsize=16, color='black')

# 设置X/Y轴的标题
plt.xlabel('x 轴')
plt.ylabel('y 轴')

# 设置X/Y轴的刻度
plt.xticks(ticks=np.arange(0,2.2,0.2))
plt.yticks(ticks=[0,2,4,6,8])

# 设置X/Y轴的范围
# plt.xlim=[0,1]
# plt.ylim=[0,6]

# 显示图例
plt.legend()  # 将每条线形图的标签label显示出来

# 显示网格
# plt.grid()

# 添加注释(文本)
# plt.text(1, 1, '交汇点', fontsize=12)

# 添加注释(文本、箭头等)
plt.annotate(text='交汇点',   # 注释的文本
             xy=(1, 1),   # 需要注释的点位置
             xytext=(0.8, 2),  # 显示注释文本的位置
             arrowprops=dict(color='grey',width=2))  # 设置注释箭头

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']  # 对中文字体进行指定,可以更改字体'SimHei'
plt.rcParams['axes.unicode_minus'] = False  # 指定中文字体后正常显示负号
plt.tight_layout()  # 紧密布局,使标题、文本等完整显示

# 输出图形
plt.show()

# 保存图形
# plt.savefig('Simple Plot.jpg')

练习0. 绘制sin曲线和cos曲线¶

In [5]:
# 根据绘图基本流程,绘制正弦曲线和余弦曲线:
# 1. 使用np.sin()方法和np.cos()方法即可绘制正弦曲线和余弦曲线
# 2. 主标题、轴标题和轴刻度等如图所示(数学符号可用r'$\pi$'显示),最终输出.jpg图片
import numpy as np
import matplotlib.pyplot as plt
%matplotlib notebook

x = np.linspace(0, 2*np.pi, 100)

plt.plot(x, np.sin(x),label='sin曲线') 
plt.plot(x, np.cos(x),label='cos曲线') 

plt.title('正弦曲线和余弦曲线')
plt.xlabel('X轴')
plt.ylabel('Y轴')
plt.xticks(ticks=np.arange(0,2*np.pi+0.1,step=np.pi/2),
           labels=('0',r'$1/2\pi$',r'$\pi$',r'2/3$\pi$',r'2$\pi$'))
# plt.xticks(np.arange(0,2*np.pi+0.1,step=np.pi/2),('0',r'$1/2\pi$',r'$\pi$',r'$3/2\pi$',r'$2\pi$'))
# plt.xticks(ticks=np.arange(0, 2*np.pi+0.1, step=np.pi/2),
#            labels=('0', '$1/2\\pi$', '$\\pi$', '2/3$\\pi$', '2$\\pi$'))

plt.legend()

plt.rcParams['font.sans-serif'] = ['SimHei']  # 对中文字体进行指定,可以更改字体'SimHei'
plt.rcParams['axes.unicode_minus'] = False  # 指定中文字体后正常显示负号
plt.tight_layout()  # 紧密布局,使标题、文本等完整显示

plt.show()
plt.savefig('sin&cos.jpg')

2. pandas对象绘制基本图形¶

除了上述的标准绘图流程,pandas对象(Series和DataFrame)也可以直接调用常用的绘图方法。

2.1 线形图 Line¶

线形图\折线图\时间序列图往往是针对时间序列所作统计图,因此折线图的横轴往往时间(顺序不能乱),纵轴是某一时刻的指标取值。将每个时间点上采集到的数据标在图上,相邻的两个点用直线连接起来就成了折线图。折线图一般用来看趋势、看周期、看突发事件并且做预测。

如果线形图的横轴是数值(连续型变量)而非时间,则可用来解读横轴和纵轴两个变量之间的关系。

image.png

例1-1 Series对象进行绘图¶

In [83]:
# 生成一个Series对象
import pandas as pd
import numpy as np
N = 50
s = pd.Series(np.random.randn(N))
s.head()
Out[83]:
0    1.270455
1    0.845237
2   -0.393019
3   -0.949524
4   -0.144077
dtype: float64

方法一:使用plt.plot()函数进行绘图(基本流程)

In [84]:
# Pandas对象可以在matplotlib画图中作为数据来源放入参数中,如.plot(Series)
plt.plot(s)
Out[84]:
[<matplotlib.lines.Line2D at 0x1769905d0>]

方法二:使用pandas对象直接调用.plot()函数

In [85]:
# Pandas对象可以直接调用画图方法,如Series.plot(),index作为x轴数据,values作为y轴数据
s.plot()
Out[85]:
<Axes: >

注意:上述语句即使不导入matplotlib也可以绘制,只是存在之前描述的两个不足,仍然可以通过导入matplotlib及其相关语句来解决。

例1-2 DataFrame对象进行绘图¶

In [13]:
# 生成一个DataFrame对象
import pandas as pd
import numpy as np
N = 100
df = pd.DataFrame([np.random.randn(N),np.random.randn(N)+5,np.random.randn(N)-5]).T
df.columns = ['Plot1','Plot2','Plot3']
df.head()
Out[13]:
Plot1 Plot2 Plot3
0 -1.816063 5.567064 -4.991131
1 0.107912 6.298941 -4.390408
2 -1.003578 4.658208 -4.700702
3 -0.201100 5.898827 -5.128678
4 -1.121558 4.238794 -5.885754

方法一:使用plt.plot()函数进行绘图(基本流程)

In [14]:
# Pandas对象可以在matplotlib画图中作为数据来源放入参数中,如.plot(DataFrame)
# 1. 直接传递df的数值一起绘制,但无法分别自定义style和label
plt.plot(df)
Out[14]:
[<matplotlib.lines.Line2D at 0x13e30ff90>,
 <matplotlib.lines.Line2D at 0x13e563a10>,
 <matplotlib.lines.Line2D at 0x13e563d50>]
In [17]:
# 2. 逐条进行绘制并设置style和label
# 主函数plot接收带有x和y的数组以及一些可选的字符串缩写参数来指定颜色color、标记marker和线类型linestyle

plt.plot(df.index,df['Plot1'],label='Plot1')
plt.plot(df.index,df['Plot2'],'r+',label='Plot2')  # 颜色为red,标记为+,线类型为空
# plt.plot(df.index,df['Plot2'],color='red',marker='+',linestyle='',label='Plot2')
plt.plot(df.index,df['Plot3'],'go--',label='Plot3')  # 颜色为green,标记为o,线类型为--
# plt.plot(df.index,df['Plot3'],color='green',marker='o',linestyle='dashed',label='Plot3')

plt.legend(loc=1)  # 将label显示出来并置于右上角
Out[17]:
<matplotlib.legend.Legend at 0x13e641110>
In [18]:
# 3. 写成循环进行绘制
style=['','r+','go--']
for i,s in zip(df.columns,style):
    plt.plot(df.index,df[i],s,label=i)
plt.legend(loc=1)
Out[18]:
<matplotlib.legend.Legend at 0x13e6e3dd0>

方法二:使用pandas对象调用.plot()函数

In [23]:
# Pandas对象可以直接调用画图方法,如DataFrame.plot(),
# index作为x轴数据,values作为y轴数据,columns作为多个图形的label
df.plot(
    style=['','r+','go--'],  # style参数:传递列表或字典设置风格,包括颜色color、标记marker和线类型linestyle
    alpha=0.9,  # alpha参数:设置不透明度
    legend=True
)
Out[23]:
<Axes: >

时间序列分析¶

In [2]:
# 导入数据,分析每天的销售额
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
data = df.groupby('订单日期')['销售额'].sum() 
data
Out[2]:
订单日期
2015-01-01    11926.250
2015-01-02    32997.160
2015-01-03    17063.172
2015-01-04     7793.184
2015-01-05     9312.184
                ...    
2018-12-25     2454.648
2018-12-26    14559.160
2018-12-28     3936.912
2018-12-29    32578.588
2018-12-30    29277.668
Name: 销售额, Length: 1239, dtype: float64
In [3]:
# 索引和切片
data['2018']  # 2018年的记录
data['2018-02']  # 2018年2月的记录
data['2018-12-01':'2018-12-15']  # 切片
Out[3]:
订单日期
2018-12-01    12384.680
2018-12-02     2229.304
2018-12-03     5676.244
2018-12-04    48955.564
2018-12-05     4650.100
2018-12-07    19255.712
2018-12-08     7850.528
2018-12-09    13185.676
2018-12-10    27484.660
2018-12-11    14491.498
2018-12-12    10353.000
2018-12-14    30329.656
2018-12-15    67945.472
Name: 销售额, dtype: float64

重采样 resample

上面的操作中,对日期进行groupby分组聚合后,时间戳是每天(D),如果想要将其转换为每月(M),可以通过重新采样来实现。重新采样是指将时间序列从一个频率转换为另一个频率的过程。将更高频率的数据聚合到低频率被称为向下采样,反之则称为向上采样。

时间序列数据的聚合可以看作是groupby的特殊用例,被称为重采样。 Pandas对象配有resample()方法,与groupby()方法类似,调用时需要对日期时间数据分组,之后再调用聚合函数。

In [4]:
# 重新采样 Resample
data = data['2018'].resample('M',kind='period').sum()  # 按月进行重新采样,传递kind参数以时间区间返回
data
Out[4]:
订单日期
2018-01    222862.829
2018-02    291814.068
2018-03    399711.781
2018-04    335531.357
2018-05    637673.162
2018-06    565523.427
2018-07    340308.682
2018-08    596338.806
2018-09    503469.855
2018-10    581870.177
2018-11    468822.543
2018-12    544539.100
Freq: M, Name: 销售额, dtype: float64

练习1. 绘制2018年12月每天销售额和利润的时间序列图¶

导入数据文件'超市.xls',数据分析得到2018年12月每天的销售额和利润,再进行绘图。

In [18]:
# 数据分析
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
data = df.groupby('订单日期')[['销售额','利润']].sum() 
data = data.loc['2018-12']
data  # 返回DataFrame对象
Out[18]:
销售额 利润
订单日期
2018-12-01 12384.680 3026.800
2018-12-02 2229.304 -149.016
2018-12-03 5676.244 713.384
2018-12-04 48955.564 15586.984
2018-12-05 4650.100 537.460
2018-12-07 19255.712 554.652
2018-12-08 7850.528 1658.188
2018-12-09 13185.676 2294.096
2018-12-10 27484.660 6095.880
2018-12-11 14491.498 3970.498
2018-12-12 10353.000 4011.840
2018-12-14 30329.656 -392.084
2018-12-15 67945.472 17257.772
2018-12-16 30721.810 -5312.090
2018-12-17 24825.696 5394.396
2018-12-18 17503.752 1215.032
2018-12-19 3689.952 158.032
2018-12-21 29249.052 5643.932
2018-12-22 37007.124 3732.764
2018-12-23 14877.632 2424.632
2018-12-24 39065.012 -166.208
2018-12-25 2454.648 -519.512
2018-12-26 14559.160 -2258.900
2018-12-28 3936.912 1185.492
2018-12-29 32578.588 7282.128
2018-12-30 29277.668 610.288
In [20]:
# 绘制时间序列图
import matplotlib.pyplot as plt
%matplotlib notebook

data.plot(
    style=['o-','^--'],
    alpha=0.8,
    title='2018年12月销售额和利润',
    ylabel='数值'
)

# plt.title('2018年12月销售额和利润')
# plt.ylabel('数值')

plt.rcParams['font.sans-serif'] = ['SimHei']  # 对中文字体进行指定,可以更改字体'SimHei'
plt.rcParams['axes.unicode_minus'] = False  # 指定中文字体后正常显示负号
plt.tight_layout()  # 紧密布局,使标题、文本等完整显示
Out[20]:
<Axes: title={'center': '2018年12月销售额和利润'}, xlabel='订单日期', ylabel='数值'>

练习2. 绘制2018年每月销售额时间序列图¶

导入数据文件'超市.xls',数据分析得到2018年每个月的销售额,再进行绘图。

In [27]:
# 数据分析
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
data = df.groupby('订单日期')['销售额'].sum()
data = data['2018']
data = data.resample('M',kind='period').sum()  # 按月进行重新采样,传递kind参数以时间区间返回
data  # 返回一个Series对象
Out[27]:
订单日期
2018-01    222862.829
2018-02    291814.068
2018-03    399711.781
2018-04    335531.357
2018-05    637673.162
2018-06    565523.427
2018-07    340308.682
2018-08    596338.806
2018-09    503469.855
2018-10    581870.177
2018-11    468822.543
2018-12    544539.100
Freq: M, Name: 销售额, dtype: float64
In [22]:
# 绘制时间序列图
data.plot(
    style='c^-.',
    alpha=0.8,
    title='2018年每月销售额',
    ylabel='销售额',
)
Out[22]:
<Axes: title={'center': '2018年每月销售额'}, xlabel='订单日期', ylabel='销售额'>

练习3. 绘制2018年不同产品类别的每月销售额的时间序列图¶

导入数据文件'超市.xls',数据分析得到2018年不同产品类别的每个月销售额,再进行绘图。

In [40]:
# 数据分析
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
# data = df[df['订单日期'].array.year==2018]
data = df.groupby(['订单日期','类别'])['销售额'].sum()  # 返回一个MultiIndex的Series对象
data = data.unstack()  # unstack(拆堆):将行中的数据透视到列(index --> columns)
data = data.loc['2018']  # DataFrame对象的行索引
data = data.resample('M',kind='period').sum()  # 按月进行重新采样,传递kind参数以时间区间返回
data  # 返回一个DataFrame对象
Out[40]:
类别 办公用品 家具 技术
订单日期
2018-01 93013.004 57794.065 72055.760
2018-02 103612.264 105159.152 83042.652
2018-03 74204.872 189553.133 135953.776
2018-04 127448.440 127647.625 80435.292
2018-05 199304.364 192803.674 245565.124
2018-06 173380.844 202455.603 189686.980
2018-07 67670.456 118898.122 153740.104
2018-08 180607.924 228681.558 187049.324
2018-09 143776.612 219038.799 140654.444
2018-10 170600.668 222196.261 189073.248
2018-11 137695.404 142531.879 188595.260
2018-12 204151.696 185287.788 155099.616
In [35]:
# 另一种数据分析方法
data = df.pivot_table(index=['订单日期'],columns=['类别'],
                      values=['销售额'], aggfunc='sum')
data = data.loc['2018']  # DataFrame对象的行索引
data = data.resample('M',kind='period').sum()  # 重采样resample
data  # 返回一个DataFrame对象
Out[35]:
销售额
类别 办公用品 家具 技术
订单日期
2018-01 93013.004 57794.065 72055.760
2018-02 103612.264 105159.152 83042.652
2018-03 74204.872 189553.133 135953.776
2018-04 127448.440 127647.625 80435.292
2018-05 199304.364 192803.674 245565.124
2018-06 173380.844 202455.603 189686.980
2018-07 67670.456 118898.122 153740.104
2018-08 180607.924 228681.558 187049.324
2018-09 143776.612 219038.799 140654.444
2018-10 170600.668 222196.261 189073.248
2018-11 137695.404 142531.879 188595.260
2018-12 204151.696 185287.788 155099.616
In [42]:
# 绘制时间序列图:风格设置为圆圈实线、方块虚线、三角点线
data.plot(
    style=['o-','s--','^-.'],
    alpha=0.8,
    title='2018年不同产品类别的每月销售额',
    ylabel='销售额',
)

plt.legend(loc=4)
Out[42]:
<matplotlib.legend.Legend at 0x14f93b5d0>

2.2 柱状图和条形图 Bar¶

柱状图是针对离散型数据(比如商品种类、性别等)所作的统计图。每一根柱子代表一个类别,柱子的高度是这个类别的数据统计量(计数、求和、求平均等)。若是竖着的柱子,成为柱状图;若是横着的柱子,成为条形图,本质上并没有什么区别,只是展示方向不同。

堆积柱状图和柱状图的本质是一样的,都是在展现不同类别的数据统计量。只不过简单的柱状图只涉及一个离散型变量(如商品类别),而堆积柱状图涉及两个离散型变量(如商品类别和客户细分)。因为涉及到两个离散型变量,每一个柱子可以代表一个类别,每一个堆叠代表另一个类别(子分类),那么这个子分类就需要通过其他手段(比如颜色最为常见)来进行区分。这样一来就需要一个额外的标签,标注该变量的不同类别所对应的颜色。

image.png

例2-1 Series对象调用plot.bar()函数¶

In [75]:
import numpy as np
import pandas as pd
%config InlineBackend.figure_format = 'retina'

s = pd.Series(np.random.randint(20,size=10),index=list('abcdefghij'))

# s.plot(kind='bar')
s.plot.bar()
# index作为X轴的类别,values作为y轴数值
Out[75]:
<Axes: >
In [76]:
# 延伸1:设置柱形的颜色和宽度
s.plot.bar(  
    color='green', # 设置颜色为green
    alpha=0.8,  # 设置不透明度为0.8
    width=0.8  # 设置柱形宽度为0.8
)
Out[76]:
<Axes: >
In [77]:
# 延伸2:调用matplotlib中内置的colormaps,使颜色随值的大小而变化
# cmap颜色参考:https://matplotlib.org/tutorials/colors/colormaps.html#sphx-glr-tutorials-colors-colormaps-py
import matplotlib.pyplot as plt
s.plot.bar(
    color=plt.get_cmap('Greens')(s/max(s)),
    alpha=0.8, 
    width=0.8
)
Out[77]:
<Axes: >

例2-2 DataFrame对象调用plot.bar()函数¶

In [66]:
import numpy as np
import pandas as pd

data=pd.DataFrame(np.random.randint(20,size=(6,4)),
                  index=pd.date_range('20201001',periods=6).date,
                  columns=list('ABCD'))

# index作为x轴的类别,values作为y轴的数据,columns作为另一个类别进行分组group或堆叠stack
# data.plot.bar()
data.plot.barh(
    stacked=True,  # 默认为group分组,设置为堆叠
    alpha=0.8, # 设置不透明度为0.8
    rot=40  # 设置label旋转角度
)
data
Out[66]:
A B C D
2020-10-01 7 3 4 19
2020-10-02 18 18 0 6
2020-10-03 7 5 10 2
2020-10-04 15 8 16 4
2020-10-05 2 13 6 9
2020-10-06 19 2 9 9

练习4. 绘制商品子类别销售额的柱状图¶

导入数据文件'超市.xls',数据分析得到每个商品子类别的销售额,返回的Series对象直接调用plot.barh()函数进行绘制。

In [67]:
# 数据分析
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
data = df.groupby('子类别')['销售额'].sum()
data  # 返回Series对象
Out[67]:
子类别
书架     2310821.100
信封      288266.300
器具     2165430.708
复印机    1995047.880
收纳具    1166020.100
标签       97582.100
桌子      862010.429
椅子     2091674.256
用具      482315.820
用品      288818.376
电话     1802056.564
系固件     129241.728
纸张      264361.020
美术      197354.864
装订机     292697.888
设备      879102.448
配件      804746.656
Name: 销售额, dtype: float64
In [68]:
# 绘制条形图:使用colormaps中'Blues',柱形颜色随着销售额大小而变化

data.plot.barh(
    color=plt.get_cmap('Blues')(data/(max(data))),
    width=0.8,
    title='每个商品子类别的销售额',
    xlabel='销售额'
)
Out[68]:
<Axes: title={'center': '每个商品子类别的销售额'}, xlabel='销售额', ylabel='子类别'>
In [73]:
# 延伸:设置一个阈值(比如10**6),销售额大于阈值的子类别用颜色'#E15759'显示,否则用颜色'#BAB0AC'显示
thred = 10**6
colors=['#BAB0AC']*data.shape[0]
for i in range(data.shape[0]):
    if data[i]>thred:
        colors[i]='#E15759'

data.plot.barh(
    color=colors,
    width=0.8,
    title='每个商品子类别的销售额',
    xlabel='销售额'
)
Out[73]:
<Axes: title={'center': '每个商品子类别的销售额'}, xlabel='销售额', ylabel='子类别'>

练习5. 绘制商品子类别中不同客户细分销售额的堆积柱状图¶

导入数据文件'超市.xls',数据分析得到每个商品子类别中不同客户细分的销售额,返回的DataFrame对象调用plot.bar()函数进行绘制。

In [78]:
# 数据分析
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
data = df.groupby(['细分','子类别'])['销售额'].sum()  # 返回一个MultiIndex的Series对象
data = data.unstack(level=0) # unstack(拆堆):将行中的数据透视到列,level=0表示将0层index透视到列
data  # 返回DataFrame对象
Out[78]:
细分 公司 小型企业 消费者
子类别
书架 728370.216 458818.948 1123631.936
信封 84092.120 47966.520 156207.660
器具 669985.036 349597.836 1145847.836
复印机 666012.144 343365.736 985670.000
收纳具 340742.360 223245.400 602032.340
标签 28648.760 20648.740 48284.600
桌子 322034.202 118465.725 421510.502
椅子 717663.114 378771.512 995239.630
用具 139173.104 90135.444 253007.272
用品 93480.660 49244.804 146092.912
电话 626298.596 303042.628 872715.340
系固件 38287.452 25765.992 65188.284
纸张 75666.220 43362.060 145332.740
美术 70228.172 36095.108 91031.584
装订机 86339.204 42047.628 164311.056
设备 223441.652 199466.848 456193.948
配件 249865.084 165745.244 389136.328
In [79]:
# 另一种数据分析方法
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
data = df.pivot_table(index = '子类别', columns = '细分',
                      values = '销售额', aggfunc = 'sum')
data  # 返回DataFrame对象
Out[79]:
细分 公司 小型企业 消费者
子类别
书架 728370.216 458818.948 1123631.936
信封 84092.120 47966.520 156207.660
器具 669985.036 349597.836 1145847.836
复印机 666012.144 343365.736 985670.000
收纳具 340742.360 223245.400 602032.340
标签 28648.760 20648.740 48284.600
桌子 322034.202 118465.725 421510.502
椅子 717663.114 378771.512 995239.630
用具 139173.104 90135.444 253007.272
用品 93480.660 49244.804 146092.912
电话 626298.596 303042.628 872715.340
系固件 38287.452 25765.992 65188.284
纸张 75666.220 43362.060 145332.740
美术 70228.172 36095.108 91031.584
装订机 86339.204 42047.628 164311.056
设备 223441.652 199466.848 456193.948
配件 249865.084 165745.244 389136.328
In [80]:
# 绘制柱形图:不同客户细分类别使用不同颜色,颜色可以参考网站:https://www.materialpalette.com/colors

colors=['#76B7B2','#BAB0AC','#FF9DA7']

data.plot.bar(
    stacked=True,
    alpha=0.8,
    width=0.8,
    color=colors,
    title='每个商品子类别中不同客户细分的销售额',
    ylabel='销售额'
)
Out[80]:
<Axes: title={'center': '每个商品子类别中不同客户细分的销售额'}, xlabel='子类别', ylabel='销售额'>

2.3 饼图 Pie¶

饼图是一种使用非常广泛的统计图,与柱状图一样,都是针对离散型数据的统计图。柱状图多用于展示频数,而饼图多用于展示频率(比例)。一个圆代表整体,然后把它切成楔形,每一个楔形代表某一个类别,所有楔形所占百分比的总和应该等于100%。饼图的块数不宜过多(将比例过少的种类归为一类,称为其它),也不宜过少(离散型变量只有两个取值时不建议画饼图),不多不少刚刚好。

image.png

In [4]:
# help(plt.pie)

例3-1 Series对象调用plot.pie() 函数¶

In [7]:
# 生成一个Series对象
import numpy as np
import pandas as pd
s = pd.Series(np.random.randint(20,size=5),index=list('abcde'))
s
Out[7]:
a     4
b     9
c     5
d    10
e     6
dtype: int64
In [11]:
# Series对象调用 plot.pie() 函数
s.name=''  # 将Series对象的name设置为'',否则会显示None
s.plot.pie(
    autopct='%.2f%%',  # 设置百分比显示格式(两位浮点数)
    # autopct='%i%%'  # 设置百分比显示格式(整数)
    pctdistance=0.7,  # 设置百分比显示的距离,默认0.6
    labeldistance=1.1,  # 设置标签显示的距离,默认1.1
    explode=[0,0.1,0,0,0],  # 设置某块楔形突出(传递与data长度相同的序列)
    startangle=90,  # 设置起始位置的角度
    counterclock=True,  # 设置饼图的方向,默认逆时针
    wedgeprops=dict(width=0.6)  # 设置饼图内径宽度,默认1.0,可将其变为环形饼图(Donut Chart)
)
Out[11]:
<Axes: >

练习6. 绘制不同地区销售额占比情况的饼图¶

导入数据文件'超市.xls',数据分析得到每个地区的销售额,返回的Series对象直接调用plot.pie()函数进行绘制,并将占比最小地区突出显示,并在合适的位置注释出销售额总量。

In [2]:
# 数据分析
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
data = df.groupby('地区')['销售额'].sum()
data
Out[2]:
地区
东北    2711223.389
中南    4147884.013
华东    4692464.994
华北    2447301.017
西北     815550.316
西南    1303124.508
Name: 销售额, dtype: float64
In [3]:
# 求销售额总额
# df['销售额'].sum()
data.sum()
Out[3]:
16117548.236999998
In [4]:
# 绘制饼图:将销售额最低的扇区突出显示,并在合适位置添加注释文本

ls=[0.] *data.shape[0]  # 创建一个值为0,长度与data相等的列表ls
ls[data.argmin()]=0.1  # 获取data中最小值的位置索引,将ls中同等位置的值设置为0.1

data.plot.pie(
    autopct='%.2f%%',
    explode=ls,  # 将ls传递给explode,最小值突出显示
    startangle=90  # 将起始位置设置为90度处
)

# 添加注释文本
plt.text(0.6,1.2,s='销售额总量:'+str(np.rint(data.sum())),fontsize=12)
Out[4]:
Text(0.6, 1.2, '销售额总量:16117548.0')

练习7. 绘制办公用品各子类别销售额占比情况的饼图¶

导入数据文件'超市.xls',数据分析得到办公用品各子类别的销售额,返回的Series对象直接调用plot.pie()函数进行绘制。由于子类别较多,将销售额最低的4个子类别合并为其它,并突出显示。

In [7]:
# 数据分析
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
data = df[df['类别']=='办公用品']  # 筛选办公用品类别
data = data.groupby(['子类别'])['销售额'].sum()  # 根据子类别分组,得到每个子类别销售总额
data0 = pd.Series(data.sort_values()[:4].sum(),index=['其它'])  # 将销售额最低的四个子类别销量求和,并转化为一个Series对象赋值给data0
data1 = data.sort_values()[4:]  # 将其余子类别切片赋值给data1
data = pd.concat([data0,data1]).sort_index()  # 使用concat()函数将data0和data1沿0轴堆叠
data.name = '办公用品各子类别销售额'  # 设置Series对象的name属性,作为饼图的标题
data
Out[7]:
信封      288266.300
其它      688539.712
器具     2165430.708
收纳具    1166020.100
用品      288818.376
装订机     292697.888
Name: 办公用品各子类别销售额, dtype: float64
In [8]:
# 绘制饼图
data.plot.pie(
    autopct='%.2f%%',  # 百分比显示两位浮点数
    pctdistance=0.7,  # 设置百分比显示的距离0.7
    explode=[0,0.1,0,0,0,0],  # 突出显示"其它"
    counterclock=False,  # 设置饼图的方向为顺时针
)
Out[8]:
<Axes: ylabel='办公用品各子类别销售额'>

2.4 直方图 Hist¶

直方图是针对连续型变量所作的统计图。直方图的横轴是实数轴,可以被分为许多连续的区间。

直方图最大的用处是观察数据分布的形态(如正态分布、右偏、左偏、t分布等),了解数据的取值范围。

image.png

一定要区分柱状图和直方图:

  • 柱状图是针对离散型数据所作的统计图,水平轴体现各个类别,每根柱子之间通常会留有空隙;
  • 直方图是针对连续型变量所作的统计图,水平轴是连续的,因此通常不留有空隙。虽然直方图的数值轴是延续性的,但整个分布依然被分成了数个柱形,每一个柱形代表的都是一些条目的集合。

例4-1 Series对象调用plot.hist() 函数¶

In [12]:
# 生成一个Series对象
import numpy as np
import pandas as pd
s = pd.Series(np.random.randn(200))  # 生成服从0-1分布的200个随机样本
s
Out[12]:
0      0.590812
1      0.852214
2      0.643126
3     -0.175887
4     -1.270769
         ...   
195    0.549132
196   -0.382942
197    0.190968
198   -0.676767
199   -1.080572
Length: 200, dtype: float64
In [10]:
# help(plt.hist)
In [13]:
# Series对象调用 plot.hist() 函数
s.plot.hist(
    bins=10,  # 设置数据桶的个数
    density=False,  # 默认为False显示频数,如果设置为Ture则显示密度,所有数值之和作归一化
    range=[-2,2],  # 设置数据显示的区间(起始位置和终止位置)
    orientation='vertical',  # 设置直方图的方向
    color='#0097a7',  # 设置颜色
    alpha=0.8  # 设置不透明度
)
Out[13]:
<Axes: ylabel='Frequency'>

练习8. 绘制每笔订单利润分布情况的直方图¶

导入数据文件'超市.xls',数据分析得到每笔订单的利润,绘制体现利润分布情况的直方图,并在合适位置注释出利润平均值。

In [14]:
# 数据分析
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
data = df.groupby('订单 ID')['利润'].sum()
data
Out[14]:
订单 ID
CN-2015-1017090     112.280
CN-2015-1031662     399.420
CN-2015-1047687      23.520
CN-2015-1070056    2866.080
CN-2015-1086295    2846.620
                     ...   
US-2018-5822232   -1664.936
US-2018-5835982    -907.732
US-2018-5840887    2758.854
US-2018-5895003    -654.416
US-2018-5897280     -50.652
Name: 利润, Length: 2773, dtype: float64
In [15]:
# 利润平均值
data.mean()
Out[15]:
777.6990685178507
In [16]:
# 绘制直方图:X轴数据显示区间设置为[-1000,2000],颜色设置为'#0097a7',并在合适位置添加注释文本

data.plot.hist(
    range=[-1000,2000],
    color='#0097a7',
    alpha=0.8,
    title='利润分布情况',
    xlabel='利润'
)

# 添加注释文本
plt.text(1000,600,s='平均利润: '+str(np.round(data.mean(),3)),fontsize=12)
Out[16]:
Text(1000, 600, '平均利润: 777.699')

2.5 散点图和气泡图 Scatter¶

散点图是用于展示两个连续型变量的一种常用统计图。散点图中的每一个点由横纵两个坐标值组成,可以用来解读两个变量的相关关系,比如正线性相关、负线性相关、非线性相关和不相关等。散点图的基本框架(用于比较两个变量): image-3.png

气泡图可以看作是带有“气泡”维度的散点图。这种图表类型的优势在于它便于我们一次比较三个连续型变量。一个变量是x轴,一个变量是y轴,而第三个则通过气泡的面积大小(还可以通过颜色)来体现。气泡图的基本框架(用于比较三个变量):

image.png

例5-1 使用plt.scatter()函数绘制散点图和气泡图¶

In [17]:
# 使用基本流程绘制散点图

N = 100
x = np.linspace(0,1,N)  # 返回 N个 0-1 之间均匀分布的样本
y = np.random.randn(N)  # 返回 N个服从标准正态分布的随机样本

plt.scatter(
    x,y,
    c='#E15759',  # 设置样本点的颜色
    s=80,       # 设置样本点的大小
    alpha=0.5,  # 设置不透明度
)

plt.show()
In [20]:
# 使用基本流程绘制气泡

N = 100
x = np.linspace(0,1,N)  # 返回 N个 0-1 之间均匀分布的样本
y = np.random.randn(N)  # 返回 N个服从标准正态分布的随机样本
z = np.random.randint(50,size=N)  # 返回 N个50以内的随机整数

plt.scatter(
    x,y,
    c=z,  # 将数值z传递给颜色参数c
    cmap='cividis',  # 设置颜色范围,可参考colormap
    s=z*10,  # 将数值z传递给大小参数s
    alpha=0.5,  # 设置不透明度
)

plt.show()

练习9. 绘制商品(设备)销售额和利润的散点图¶

导入数据文件'超市.xls',数据分析得到“设备”这一类别商品的销售额和利润,使用plt.scatter()函数进行绘制。

In [21]:
# 数据分析
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
data=df[df['子类别']=='设备']  # 筛选子类别为设备的记录
data.head()
Out[21]:
订单 ID 订单日期 发货日期 邮寄方式 客户 ID 客户名称 细分 城市 省/自治区 国家 地区 产品 ID 类别 子类别 产品名称 销售额 数量 折扣 利润
行 ID
6 CN-2016-4497736 2016-10-27 2016-10-31 标准级 俞明-18325 俞明 消费者 景德镇 江西 中国 华东 技术-设备-10001640 技术 设备 柯尼卡 打印机, 红色 11129.58 9 0.0 3783.78
11 CN-2015-4195213 2015-12-22 2015-12-24 二级 谢雯-21700 谢雯 小型企业 榆林 陕西 中国 西北 技术-设备-10000001 技术 设备 爱普生 计算器, 耐用 434.28 2 0.0 4.20
62 CN-2017-4162714 2017-06-07 2017-06-10 一级 马丽-16480 马丽娜 消费者 邓州 河南 中国 中南 技术-设备-10004500 技术 设备 松下 电话, 耐用 2775.36 7 0.0 332.22
65 CN-2018-5922906 2018-09-15 2018-09-17 二级 钱伟-21430 钱伟 公司 湛江 广东 中国 中南 技术-设备-10003635 技术 设备 StarTech 打印机, 耐用 4784.08 4 0.0 382.48
99 CN-2018-5834341 2018-11-10 2018-11-14 标准级 马惠-14335 马惠英 公司 南宁 广西 中国 中南 技术-设备-10003039 技术 设备 爱普生 收据打印机, 耐用 2186.80 4 0.0 612.08
In [23]:
# 绘制散点图,点的颜色和大小固定

plt.scatter(
    x=data['销售额'],
    y=data['利润'],
    s=50,
    c='c',
    alpha=0.8
)

plt.title('设备类商品销售额和利润分布情况')
plt.xlabel('销售额')
plt.ylabel('利润')

plt.tight_layout()
plt.show()
In [24]:
# 还可进一步分析相关关系的原因,将'折扣'作为颜色传递参数
plt.scatter(
    x=data['销售额'],
    y=data['利润'],
    c=data['折扣'],
    alpha=0.8
)

plt.title('设备类商品销售额和利润分布情况')
plt.xlabel('销售额')
plt.ylabel('利润')

plt.tight_layout()
plt.show()

练习10. 绘制2018年浙江省每笔订单销售额和利润的气泡图¶

导入数据文件'超市.xls',数据分析得到2018年浙江省每一笔订单的销售额、利润和购买商品数量,使用plt.scatter()函数绘制气泡图。

In [25]:
# 数据分析
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
df = df[(df['订单日期'].array.year==2018)&(df['省/自治区']=='浙江')]
data = df.groupby('订单 ID')[['销售额','利润','数量']].sum()
data
Out[25]:
销售额 利润 数量
订单 ID
CN-2018-1182921 7114.968 -1460.732 16
CN-2018-1246652 10696.588 -4138.232 13
CN-2018-1510100 14931.084 -7248.136 30
CN-2018-1516102 1130.388 396.228 17
CN-2018-1574908 6917.400 -471.240 12
... ... ... ...
US-2018-5180154 3545.696 -439.264 24
US-2018-5235171 1621.648 -795.592 5
US-2018-5315398 189.000 -37.800 5
US-2018-5444149 165.480 -24.920 5
US-2018-5726249 14059.892 -6876.828 20

65 rows × 3 columns

In [26]:
# 绘制气泡图:点的颜色大小都随数量的大小而变化

plt.scatter(
    x=data['销售额'],
    y=data['利润'],
    s=data['数量']*15,
    c=data['数量'],
    alpha=0.6
)

plt.title('2018年浙江省订单销售额和利润分布情况')
plt.xlabel('销售额')
plt.ylabel('利润')

plt.tight_layout()
plt.show()
In [27]:
# 延伸:不同类别的订单使用不同颜色进行标记
# 数据分析:2018年浙江省不同类别订单的销售额和利润
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
df = df[(df['订单日期'].array.year==2018)&(df['省/自治区']=='浙江')]
data = df.groupby(['类别','订单 ID'])[['销售额','利润']].sum()
data
Out[27]:
销售额 利润
类别 订单 ID
办公用品 CN-2018-1182921 47.040 -18.060
CN-2018-1246652 421.120 37.800
CN-2018-1510100 5397.924 -2004.016
CN-2018-1516102 1130.388 396.228
CN-2018-1574908 2153.256 349.216
... ... ... ...
技术 US-2018-4479623 3186.792 -425.208
US-2018-4774798 245.952 -45.108
US-2018-4988987 2110.416 -70.504
US-2018-5180154 601.020 -95.340
US-2018-5235171 1553.328 -802.872

101 rows × 2 columns

In [28]:
# 绘制气泡图,将plt.scatter()函数写入for循环中
# 三个类别的颜色设置为['#4db6ac','#ff8a65','#78909c'],建议写入for循环中

colors = ['#4db6ac','#ff8a65','#78909c']
for i,c in zip(data.index.levels[0],colors):
    plt.scatter(
        x=data.loc[i,['销售额']],
        y=data.loc[i,['利润']],
        label=i,
        alpha=0.6,
        s=120,
        c=c
    )

plt.title('2018年浙江省不同类别订单销售额和利润分布情况')
plt.xlabel('销售额')
plt.ylabel('利润')

plt.legend()
plt.show()

3. pandas对象绘制多子图 Subplots¶

对于pandas对象(Series和DataFrame),我们学习了直接使用pandas对象调用绘图方法,比如.plot()绘制线形图、.plot.bar()绘制柱状图、.plot.pie()绘制饼图、.plot.hist()绘制直方图。因此在绘制多子图时,pandas对象可以直接调用绘图方法,然后再将子图的轴axs传递给参数ax即可。

而对于散点图/气泡图Scatter,需要使用轴axs调用.scatter()函数进行绘制。

In [29]:
# 创建一个Series对象
import pandas as pd
N = 100
s = pd.Series(np.random.randn(N),index=np.linspace(0,1,N))
s
Out[29]:
0.000000   -2.122179
0.010101    0.473741
0.020202    0.640655
0.030303    0.051061
0.040404   -1.177804
              ...   
0.959596    0.304418
0.969697    0.253425
0.979798    0.767768
0.989899    0.424501
1.000000    0.033509
Length: 100, dtype: float64
In [41]:
# Series对象绘制多子图
# import matplotlib.pyplot as plt
# %matplotlib notebook

# 使用plt.subplots()创建图片和多个轴
fig, axs = plt.subplots(2, 2)  # 创建一张2*2个轴的图片,返回以生成子图对象的NumPy数组

# 使用pandas对象直接调用绘图方法,将轴传递给ax参数即可
s.plot(ax=axs[0,0], style='c')
s.plot.hist(ax=axs[0,1], grid=False)

# 对于scatter函数,使用基本流程绘制
axs[1,0].scatter(s.index,s)
axs[1,1].scatter(s.index,s,c=s,s=(s+5)*5)
# axs[1,0].plot(s.index,s,color='c')
# axs[1,1].hist(s)

# 进一步设置标题和X\Y轴标题
axs[0,0].set_title('Line Plot')
axs[0,1].set_title('Hist Plot')
axs[1,0].set_ylabel('Y-Axis')
axs[1,1].set_xlabel('X-Axis')
Out[41]:
Text(0.5, 0, 'X-Axis')

练习11. 绘制两行一列多子图¶

  • 上图绘制线形图(时间序列图):2018年每个月的销售额和利润。
  • 下图绘制柱状图:2018年每个月的订单数量。
  • 共享X轴:均为日期(以月份为刻度)。
In [45]:
# 数据分析:得到2018年每个月的销售额和利润
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
data1 = df.groupby(['订单日期'])[['销售额','利润']].sum()
data1 = data1.loc['2018'].resample('M',kind='period').sum()
data1
Out[45]:
销售额 利润
订单日期
2018-01 222862.829 33526.269
2018-02 291814.068 35249.508
2018-03 399711.781 56542.521
2018-04 335531.357 32848.277
2018-05 637673.162 67969.062
2018-06 565523.427 81670.267
2018-07 340308.682 58861.922
2018-08 596338.806 58370.326
2018-09 503469.855 80374.595
2018-10 581870.177 73466.617
2018-11 468822.543 31554.383
2018-12 544539.100 74556.440
In [47]:
# 数据分析:得到2018年每个月的订单数量
data2 = df.drop_duplicates('订单 ID')  # 删除重复的订单ID
data2 = data2.groupby('订单日期')['订单 ID'].count()
data2 = data2.loc['2018'].resample('M',kind='period').sum()
data2
Out[47]:
订单日期
2018-01    37
2018-02    47
2018-03    42
2018-04    48
2018-05    93
2018-06    81
2018-07    61
2018-08    83
2018-09    72
2018-10    85
2018-11    81
2018-12    87
Freq: M, Name: 订单 ID, dtype: int64
In [48]:
# 绘制多子图

fig, axs = plt.subplots(nrows=2, ncols=1)
data1.plot(ax=axs[0],style=['o-','s--'], sharex=True)
data2.plot.bar(ax=axs[1],rot=40,color=plt.get_cmap('Blues')(data2/(max(data2))))

axs[0].set_title('2018年销售情况分析')
axs[0].set_ylabel('值')
axs[1].set_ylabel('订单数量')
Out[48]:
Text(0, 0.5, '订单数量')

练习12. 绘制一行两列多子图¶

  • 左图绘制饼图:2018年某商品(纸张)在不同地区销售额占比情况。
  • 右图绘制散点图:2018年某商品(纸张)每笔订单销售额和利润的分布情况,不同地区用不同颜色表示。
In [49]:
# 数据分析:2018年某商品(纸张)在不同地区销售总额
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
df = df[(df['订单日期'].array.year==2018)&(df['子类别']=='纸张')]
data1 = df.groupby('地区')['销售额'].sum()
data1
Out[49]:
地区
东北    12644.10
中南    21396.20
华东    26290.46
华北    16109.10
西北     2186.10
西南     5861.94
Name: 销售额, dtype: float64
In [50]:
# 数据分析:2018年某商品(纸张)每笔订单的销售额和利润
data2 = df.groupby(['地区','订单 ID'])[['销售额','利润']].sum()
data2
Out[50]:
销售额 利润
地区 订单 ID
东北 CN-2018-1312334 450.80 144.20
CN-2018-2102819 568.40 136.22
CN-2018-2292031 1526.56 526.96
CN-2018-2306215 237.30 101.64
CN-2018-2560726 426.72 119.28
... ... ... ...
西南 US-2018-1069751 248.08 76.72
US-2018-2162302 422.80 46.20
US-2018-4339927 124.32 33.46
US-2018-4764837 451.64 157.92
US-2018-5315398 435.96 121.80

165 rows × 2 columns

In [52]:
# 绘制多子图

fig, axs = plt.subplots(nrows=1, ncols=2, figsize=(8,4))

# 绘制饼图
data1.plot.pie(ax=axs[0],autopct='%i%%',pctdistance=0.7)

# 绘制散点图,使用for循环遍历不同地区
for i in data2.index.levels[0]:
    axs[1].scatter(
        x = data2.loc[i,'销售额'],
        y = data2.loc[i,'利润'],
        label = i,
        alpha = 0.7
    )

axs[0].set_title('纸张在不同地区销售额占比情况')
axs[1].set_title('纸张的销售额和利润分布情况')
axs[1].set_xlabel('Salse')
axs[1].set_ylabel('Profit')
axs[1].legend(loc=4)
Out[52]:
<matplotlib.legend.Legend at 0x16d7d1e10>